🐱 算神的小窝 🤓

实现实时查询股票信息的MCP Server.md


CreationTime:5/27/2025 2:29:51 PM LastAccessTime:6/30/2025 2:30:05 PM


C#实现实时查询股票信息的MCP Server

本篇使用的是新浪财经网页的股票查询接口,仅供学习研究演示之用。

什么是MCP

MCP 是由 Anthropic2024 年 11 月 推出的开源协议,旨在为大型语言模型(LLM)提供统一的接口标准,实现与外部数据源、工具及服务的无缝连接。它被类比为 “AI 领域的 USB-C 接口”“万能插头”,目标是解决传统 AI 集成中的碎片化问题,降低开发成本,提升实时性与安全性。

实战

调用股票查询接口

  • 创建.Net 9项目

  • SinaStockClient.cs

    public partial class SinaStockClient
    {
      private const string SINA_API_BASE = "https://hq.sinajs.cn/list";
      private static readonly HttpClient client = new();
    
      static SinaStockClient()
      {
          client.DefaultRequestHeaders.Referrer = new Uri("https://finance.sina.com.cn");
      }
    
      public static async Task<StockQuote?> MakeStockRequest(string symbol)
      {
          var url = $"{SINA_API_BASE}={symbol}";
    
          try
          {
              var response = await client.GetByteArrayAsync(url);
              Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
              var text = Encoding.GetEncoding("GB2312").GetString(response);
              return ParseSinaStockData(text, symbol);
          }
          catch (Exception ex)
          {
              Console.WriteLine($"Error making stock request: {ex.Message}");
              return null;
          }
      }
    
      private static StockQuote? ParseSinaStockData(string text, string symbol)
      {
          var match = StockRegex().Match(text);
          if (!match.Success) return null;
    
          var values = match.Groups[1].Value.Split(',');
          if (values.Length < 32) return null;
    
          return new StockQuote
          {
              Symbol = symbol,
              Name = values[0],
              Open = values[1],
              Close = values[2],
              Price = values[3],
              High = values[4],
              Low = values[5],
              Volume = values[8],
              Amount = values[9],
              Date = values[30],
              Time = values[31]
          };
      }
    
      public static string FormatQuote(StockQuote quote)
      {
          if (quote == null) return "Invalid quote data";
    
          var changeRate = (Convert.ToDouble(quote.Price) - Convert.ToDouble(quote.Close))
                          / Convert.ToDouble(quote.Close) * 100;
    
          return string.Join("\n",
              "---",
              $"Stock Code: {quote.Symbol}",
              $"Stock Name: {quote.Name}",
              $"Current Price: ¥{quote.Price}",
              $"Change Rate: {changeRate:F2}%",
              $"Open Price: ¥{quote.Open}",
              $"High Price: ¥{quote.High}",
              $"Low Price: ¥{quote.Low}",
              $"Volume: {Convert.ToDouble(quote.Volume) / 100:F0} lots",
              $"Turnover: ¥{Convert.ToDouble(quote.Amount) / 10000:F2} million",
              $"Update Time: {quote.Date} {quote.Time}",
              "---"
          );
      }
    
      [GeneratedRegex("\"(.*)\"")]
      private static partial Regex StockRegex();
    }
    
    public record StockQuote
    {
        public string? Symbol { get; set; }
        public string? Name { get; set; }
        public string? Open { get; set; }
        public string? Close { get; set; }
        public string? Price { get; set; }
        public string? High { get; set; }
        public string? Low { get; set; }
        public string? Volume { get; set; }
        public string? Amount { get; set; }
        public string? Date { get; set; }
        public string? Time { get; set; }
    }
    
  • 调用测试

    var quote = await SinaStockClient.MakeStockRequest("sh600000");
    if (quote != null)
    {
      Console.WriteLine(SinaStockClient.FormatQuote(quote));
    }
    

实现MCP Server

  • 安装NuGet包:

    dotnet add package ModelContextProtocol.AspNetCore
    
  • 改造一下SinaStockClient.cs,以适应DI注入

    private readonly HttpClient client;
    
    public SinaStockClient(IHttpClientFactory ClientFactory)
    {
        client = ClientFactory.CreateClient();
        client.DefaultRequestHeaders.Referrer = new Uri("https://finance.sina.com.cn");
    }
    
  • 定义MCP工具类

    [McpServerToolType]
    public static class StockTool
    {
        [McpServerTool]
        [Description("Get real-time stock quote")]
        public static async Task<string> GetQuote(IHttpClientFactory ClientFactory, [Description("Stock symbol (e.g.: sh600000, sz000001)")] string symbol)
        {
            SinaStockClient stockClient = new(ClientFactory);
            var quote = await stockClient.MakeStockRequest(symbol);
            if (quote != null)
                return stockClient.FormatQuote(quote);
            return "没有找到这只股票的数据";
        }
    }
    
  • Program.cs中配置服务:

    builder.Services.AddHttpClient(); // 注册HttpClient
    builder.Services.AddMcpServer().WithHttpTransport().WithToolsFromAssembly(); // 自动注册工具,WithHttpTransport()为采用SSE模式返回MCP
    app.MapMcp(); // 映射MCP端点
    

    生产小技巧:如果需要整合在已有的Asp.Net CoreBlazor Server项目中,可以这样映射端点,避免路由冲突错误

    app.MapMcp("mcp"); // 映射MCP端点
    
  • Microsoft.SemanticKernel.Connectors.Ollama中使用MCP Server,部分关键代码:

    private const string MCP_SERVER = "https://localhost:44328/mcp/sse"; // MCP Server的url,由于我是整合在现有项目中所有多了一个/mcp
    
    private readonly ChatHistory history = [];
    private IMcpClient? mcpClient;
    [Inject]
    private Kernel? Kernel { get; set; }
    
    // Enable automatic function calling
    private static readonly OpenAIPromptExecutionSettings executionSettings = new()
    {
        Temperature = 0,
        FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(options: new() { RetainArgumentTypes = true })
    };
    
    // 创建一个 SSE(Server-Sent Events)客户端传输配置实例
    var config = new SseClientTransport(
        // 配置传输选项,指定服务端点(Endpoint)
        new SseClientTransportOptions()
        {
            // 设置远程服务器的 URI 地址  (记得替换真实的地址,从魔搭MCP广场获取)
            Endpoint = new Uri(MCP_SERVER)
        }
    );
    mcpClient ??= await McpClientFactory.CreateAsync(config);
    // 调用客户端的 ListToolsAsync 方法,获取可用工具列表
    var listToolsResult = await mcpClient.ListToolsAsync();
    Kernel!.Plugins.AddFromFunctions("MyMCP", listToolsResult.Select(aiFunction => aiFunction.AsKernelFunction()));
    
    history.AddUserMessage(prompt);
    
    var ollamaChatClient = new OllamaApiClient(OllamaUri, modelname + ":latest");
    client = new ChatClientBuilder(ollamaChatClient)
                // 👇🏼 Add function invocation to the chat client, wrapping the Ollama client
                .UseFunctionInvocation()
                .Build();
    chat = client.AsChatCompletionService();
    
    await foreach (var resp in chat.GetStreamingChatMessageContentsAsync(history, executionSettings: executionSettings, kernel: Kernel))
    {
        Answer.Response += resp.Content;
        StateHasChanged();
    }
    
  • 最终运行效果图

    110716

An unhandled error has occurred. Reload 🗙